// ============================================================================
// Core 2
// 
// Description: 
// Splits the job into multiple smaller tasks and assigns them to either core 0
// or core 1 by placing them in a queue. Results of processed tasks are 
// collected from the queue and are written back to frame buffer.
// 
// ============================================================================

#include "memmap.h"
#include "compsoc.h"

#include "../filter_ex3_common/structures.h"

// ============================================================================
// Data mapping
// ============================================================================

// Use the global memory as the task queue
const Queue* taskQueue = (void*) shared_mem_pt;

// Use the frame buffer as the source image
const Image* sourceImage = (void*) frame_mem_pt;

// Use rdma0 as a temporary slot
DMA dma0 = {1, (void*) dma0_BASEADDR};
Slot* tempSlot = (void*) cmemout0_BASEADDR;

// Use tile 0 and 1 synchronization registers as queue states
const TileQueueState* stateTileQueue0 = (void*) mb0_cmemin0_pt;
const TileQueueState* stateTileQueue1 = (void*) mb1_cmemin0_pt;

const TileQueueState* stateTileQueues[2] = {(TileQueueState*) mb0_cmemin0_pt, (TileQueueState*) mb1_cmemin0_pt};

// Use rdma0 as a temporary slot state
SlotState* stateTemp = (void*) cmemout0_BASEADDR;

// Use synchronization register as queue state
QueueState* stateQueue = (void*) cmemin0_BASEADDR;

// ============================================================================
// Transfer functions
// ============================================================================

// Reads/writes rows from the source image to the temp slot
void readRowsFromImage(int srcStartRow, int numberOfRows, int destStartRow) {
	
	hw_dma_receive((void*) &tempSlot->rows[destStartRow], (void*) &sourceImage->rows[srcStartRow], (sizeof(Row) * numberOfRows) / 4, &dma0);
}

void writeRowsToImage(int destStartRow, int numberOfRows, int srcStartRow) {

	hw_dma_send((void*) &sourceImage->rows[destStartRow], (void*) &tempSlot->rows[srcStartRow], (sizeof(Row) * numberOfRows) / 4, &dma0);
}

// Read/write slot from the task queue to the temp slot
void readSlotFromQueue(int tile, int slot) {

	hw_dma_receive((void*) tempSlot, (void*) &taskQueue->tiles[tile].slots[slot], sizeof(Slot) / 4, &dma0);
}

void writeSlotToQueue(int tile, int slot) {

	hw_dma_send((void*) &taskQueue->tiles[tile].slots[slot], (void*) tempSlot, sizeof(Slot) / 4, &dma0);
}

// Write slot state
void updateQueueSlot(int tile, int slot, SlotState newState) {

	*stateTemp = newState;

	hw_dma_send((void*) &stateTileQueues[tile]->slots[slot], (void*) stateTemp, sizeof(SlotState) / 4, &dma0);
}

// Ensure all writes have been processed
void flushWrites() {

	hw_dma_receive((void*) tempSlot, (void*) sourceImage, 1, &dma0);
}

// ============================================================================
// Main entry point
// ============================================================================

int main() {

	// Initialize
	mon_sync(3);

	print_debug(1);

	// Save line 160
	// we will need this line when processing the last row of tile 0, 
	// but at that point it will already be overwritten by tile 1.
	readRowsFromImage(160, 1, 0);
	
	Row line160 = tempSlot->rows[0];

	// Flag indicating if the tile is done
	int tileDone[2] = {0, 0};

	// The last row that we send for processing to the tiles
	int lineNo[2] = {0, 159};

	while(1) {

		// If both tiles are done we can exit the loop
		if (tileDone[0] && tileDone[1]) {
			break;
		}

		// Loop through each slot in both queues
		int i;
		for (i = 0; i < QUEUE_LENGTH; i++) {

			int j;
			for (j = 0; j < TILE_NUM; j++) {

				SlotState slotState = stateQueue->tiles[j].slots[i];

				if (slotState.state == STATE_EMPTY && ((j == 0 && lineNo[0] < 161) || (j == 1 && lineNo[1] < 321))) {

					if (j == 1 && lineNo[1] == 159) {

						// First line of core 1
						readRowsFromImage(159, 1, 0);
						writeSlotToQueue(j, i);

						SlotState newState;
						newState.state = STATE_CORE1START;
						newState.startRow = 159;

						updateQueueSlot(j, i, newState);
						stateQueue->tiles[j].slots[i] = newState;

						lineNo[1] += 1;

						print_debug(j * 10 + 2);

					} else if (j == 0 && lineNo[0] == 160) {

						// Last line of core 0
						tempSlot->rows[0] = line160;
						writeSlotToQueue(j, i);

						SlotState newState;
						newState.state = STATE_CORE0END;
						newState.startRow = 160;

						updateQueueSlot(j, i, newState);
						stateQueue->tiles[j].slots[i] = newState;

						lineNo[0] += 1;

						print_debug(j * 10 + 3);

					} else if (j == 1 && lineNo[1] == 320) {

						// Last line of core 1
						SlotState newState;
						newState.state = STATE_CORE1END;
						newState.startRow = 320;

						updateQueueSlot(j, i, newState);
						stateQueue->tiles[j].slots[i] = newState;

						lineNo[1] += 1;

						print_debug(j * 10 + 3);

					} else {

						// Default case

						// Receive next 5 rows from source image
						readRowsFromImage(lineNo[j], 5, 0);

						// Fill queue slot of tile
						writeSlotToQueue(j, i);
			
						// Update state of queue slot of tile
						SlotState newState;
						newState.state = STATE_UNPROCESSED;
						newState.startRow = lineNo[j];

						updateQueueSlot(j, i, newState);
						stateQueue->tiles[j].slots[i] = newState;

						// Update line number
						lineNo[j] += 5;

						print_debug(j * 10 + 4);
					}

				} else if (slotState.state == STATE_PROCESSED) {

					// Read 5 processed rows

					// Retreive queue slot from tile
					readSlotFromQueue(j, i);

					// Send rows to the source image
					writeRowsToImage(slotState.startRow - 1, 5, 0);

					// Check if tile is done
					if (j == 1 && slotState.startRow == 320 - 5) {
						tileDone[1] = 1;
						print_debug(j * 10 + 7);
					}

					// Mark slot as empty
					stateQueue->tiles[j].slots[i].state = STATE_EMPTY;

					print_debug(j * 10 + 5);

				} else if (slotState.state == STATE_PROCESSED4) {

					// Read 4 processed rows

					// Retreive queue slot from tile
					readSlotFromQueue(j, i);

					// Send rows to the source image
					writeRowsToImage(slotState.startRow, 4, 0);

					// Check if tile is done
					if (j == 1 && slotState.startRow == 320 - 5) {
						tileDone[1] = 1;
						print_debug(j * 10 + 7);
					}

					// Mark slot as empty	
					stateQueue->tiles[j].slots[i].state = STATE_EMPTY;

					print_debug(j * 10 + 9);

				} else if (slotState.state == STATE_PROCESSED1) {
					
					// Read 1 processed row
					readSlotFromQueue(j, i);
					writeRowsToImage(slotState.startRow - 1, 1, 0);
					stateQueue->tiles[j].slots[i].state = STATE_EMPTY;

					tileDone[j] = 1;

					print_debug(j * 10 + 6);
				}
			}
		}
	}

	// Wait until all data has been send to the frame buffers
	flushWrites();

	print_debug(8);

	// Print result
	print_framebuffer();

	return 0;
}
